package ru.ifmo.croak.impl.replication.team1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.ifmo.croak.api.PostService;
import ru.ifmo.croak.api.QueryService;
import ru.ifmo.croak.impl.replication.team1.sync.SynchronizerManager;

import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public class LocalContext {
    private static final Logger log = LoggerFactory.getLogger(LocalContext.class);

    private final UUID localUUID;
    private final PostService postService;
    private final QueryService queryService;
    private final AliveHostsCollection aliveHostsCollection;

    void addAliveHost(SocketAddress socketAddress) {
        aliveHostsCollection.addAliveHost(socketAddress);
    }

    public void removeAliveHost(SocketAddress socketAddress) {
        aliveHostsCollection.removeAliveHost(socketAddress);
    }

    public SocketAddress getRandomAliveHost() {
        return aliveHostsCollection.getRandomAliveHost();
    }

    private final AtomicLong localTime;
    private final ConcurrentHashMap<UUID, Long> localTimeOfArrival;
    private UUID xor;
    volatile Pair<Long, UUID> timeAndXorPair;


    public Pair<Long, UUID> getLocalTimeAndXor() {
        return timeAndXorPair;
    }

    public static UUID xorUUID(UUID a, UUID b) {
        return new UUID(a.getMostSignificantBits() ^ b.getMostSignificantBits(),
                a.getLeastSignificantBits() ^ b.getLeastSignificantBits());
    }

    /**
     * @param messageUUID uuid
     * @return true if added
     */
    public boolean putMessageIntoLocalTimeOfArrivalAndUpdateXor(UUID messageUUID) {
        synchronized (localTimeOfArrival) {
            log.trace("putMessageIntoLocalTimeOfArrivalAndUpdateXor {}", messageUUID);
            log.trace("old localTimeOfArrival {}", localTimeOfArrival);
            if (localTimeOfArrival.containsKey(messageUUID)) {
                log.trace("update unneeded {}", messageUUID);
                return false;
            }
            localTimeOfArrival.put(messageUUID, localTime.incrementAndGet());
            log.trace("new localTimeOfArrival {}", localTimeOfArrival);
            log.trace("old xor {}", xor);
            xor = xorUUID(xor, messageUUID);
            log.trace("new xor {}", xor);
            log.trace("update success {}", messageUUID);
            timeAndXorPair = new Pair<Long, UUID>(localTime.longValue(), xor);
            return true;
        }
    }

    public Long getLocalTimeOfArrival(UUID messageUUID) {
        return localTimeOfArrival.get(messageUUID);
    }


    public AtomicLong getLocalTime() {
        return localTime;
    }

    private int synchronizerTCPPort;
    private SynchronizerManager synchronizerManager;


    public LocalContext(PostService postService, QueryService queryService) {
        this.postService = postService;
        this.queryService = queryService;
        aliveHostsCollection = new AliveHostsCollection();
        localTime = new AtomicLong(0);
        xor = new UUID(0, 0);
        timeAndXorPair = new Pair<Long, UUID>(localTime.longValue(), xor);
        localTimeOfArrival = new ConcurrentHashMap<UUID, Long>();
        localUUID = UUID.randomUUID();
        synchronizerTCPPort = -1;
    }

    public UUID getLocalUUID() {
        return localUUID;
    }

    public SynchronizerManager getSynchronizerManager() {
        return synchronizerManager;
    }

    public void setSynchronizerManager(SynchronizerManager synchronizerManager) {
        this.synchronizerManager = synchronizerManager;
    }

    public PostService getPostService() {
        return postService;
    }


    public QueryService getQueryService() {
        return queryService;
    }


    public int getSynchronizerTCPPort() {
        return synchronizerTCPPort;
    }

    public void setSynchronizerTCPPort(int synchronizerTCPPort) {
        this.synchronizerTCPPort = synchronizerTCPPort;
    }
}
